package com.ashafa.server;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletException;

import java.io.File;
import java.io.IOException;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import java.util.HashMap;

import com.ashafa.cache.DomCache;
import com.ashafa.fileutils.*;

import org.mozilla.javascript.*;
import org.mortbay.log.*;
import org.envjson.*;


public class Servlet extends HttpServlet {
	
	private static final Logger logger = Log.getLogger(Servlet.class.getName()); 
	private static String contextsPath;
	private static String installPath;
	
	private Script envJs;
	
	
	private String modulesPath;
	private String thisContextPath;
	
	private HashMap moduleTimers;
	private JSONArray urlsArray;
		
	private ScriptableObject coreScope;
	private ScriptableObject sharedScope;
	private DomCache domCache;
	private Routing routing;
	
	
	public static void setContextsPath(String path){
		contextsPath = path;
	}
	
	public static void setInstallPath(String path){
		installPath = path;
	}
	
	private void setURLs() throws JSONException{
		urlsArray = new JSONObject(FetchFile.readFile(thisContextPath + "/urls.json", "{urls:[]}")).getJSONArray("urls");
	}
	
	
	private void startUrlsWatcher() throws Exception{
		
		TimerTask urlsWatcherTask = new FileWatcher(new File(thisContextPath+ "/urls.json")){
			protected void onChange(File file) throws Exception {
				urlsArray = null;
			}
		};
			
		new Timer().schedule(urlsWatcherTask, new Date(), 1000);
		
	}
	
	private void startModuleWatcher(String filename) throws Exception{
		File file = new File(filename);
		if(!moduleTimers.containsKey(file.getAbsolutePath())){
			TimerTask moduleWatcherTask = new FileWatcher(file){
				protected void onChange(File file) throws Exception {
					if(file.exists()){
						Context cx = new CustomContextFactory().enterContext();
						try
						{
							try{
								cx.evaluateString(sharedScope, FetchFile.readFile(file.getAbsolutePath()), file.getAbsolutePath(), 1 , null);
							} catch(Exception e){
								logger.warn("Error Loading Module:" + file.getAbsolutePath()  , e);
							} 
						} finally {
							Context.exit();
						}
					} else {
						Timer moduleTimer = (Timer) moduleTimers.get(file.getAbsolutePath());
						moduleTimer.cancel();
						moduleTimers.remove(file.getAbsolutePath());
					}
				}
			};
			Timer moduleTimer =  new Timer();
			moduleTimer.schedule(moduleWatcherTask, new Date(), 1000);
			moduleTimers.put(new File(filename).getAbsolutePath(), moduleTimer);
		}
	}
		
	
	private void startModuleDirWatcher(String directory) throws Exception{
		
		TimerTask moduleWatcherTask = new DirWatcher(new File(directory)){
			protected void onChange(File dir) throws Exception {
				Context cx = new CustomContextFactory().enterContext();
				try
				{
					loadModules(modulesPath, cx, false);
				} finally {
					Context.exit();
				}
			}
		};
			
		new Timer().schedule(moduleWatcherTask, new Date(), 1000);
	}
	
	
	private void loadModules(String modulesPath, Context cx, boolean startWatcher) throws ServletException{
		
		File modulesDirectory = new File(modulesPath);
		if(modulesDirectory.isDirectory() && modulesDirectory.exists()){
			String[] contextModulesDirList = modulesDirectory.list();
			
			for(int i = 0; i < contextModulesDirList.length; i++)
			{
				if(contextModulesDirList[i].endsWith(".js"))
				{
					try{
						cx.evaluateString(sharedScope, FetchFile.readFile(modulesPath+ "/" + contextModulesDirList[i], ""), modulesPath+"/"+contextModulesDirList[i]+"", 1 , null);
					} catch(Exception e){
						logger.warn("Error Loading Module:" + modulesPath+ "/" + contextModulesDirList[i]  , e);
					} 
						
					try{
						startModuleWatcher(modulesPath+ "/" + contextModulesDirList[i]);
					} catch(Exception e){
						throw new ServletException(e);
					} 
					
				}
			}
		}
	}
	
	
		
	public Servlet() {
	
		
	}
	
	
	
	
	public void init() throws ServletException {
		
		thisContextPath = contextsPath + getServletContext().getContextPath();
		modulesPath = thisContextPath + "/modules";
		domCache = new DomCache();
		routing = new Routing();
		moduleTimers = new HashMap();
		
		
		
		Context cx = new CustomContextFactory().enterContext();
		
		try
		{
			coreScope = new ImporterTopLevel(cx);
						
			coreScope.put("$log", coreScope,  Context.javaToJS(Log.getLogger(getServletContext().getContextPath()), coreScope));
			
			cx.evaluateString(coreScope, FetchFile.readFile(installPath + "/core-js/env.js", ""), "core/env.js", 1, null);
			cx.evaluateString(coreScope, FetchFile.readFile(installPath + "/core-js/date.js", ""), "core/date.js", 1, null);
			cx.evaluateString(coreScope, FetchFile.readFile(installPath + "/core-js/jquery.js", ""), "core/jquery.js", 1, null);
			cx.evaluateString(coreScope, FetchFile.readFile(installPath + "/core-js/core.js", ""), "core/core.js", 1, null);
			
			envJs = cx.compileString(FetchFile.readFile(installPath + "/core-js/env.js", ""), "env ", 1, null);
			
			
			
			ScriptableObject core = (ScriptableObject)coreScope.get("$core", coreScope);
			
			ScriptableObject.putProperty(core, "javaDomCache", Context.javaToJS(domCache, core));
			ScriptableObject.putProperty(core, "servletContextPath", thisContextPath);
			ScriptableObject.putProperty(core, "installPath", installPath);
			ScriptableObject.putProperty(core, "controllerManager", Context.javaToJS(new ControllerManager(getServletContext().getContextPath(), thisContextPath, coreScope), core));
			ScriptableObject.putProperty(core, "pluginManager", Context.javaToJS(new PluginManager(installPath + "/plugins", coreScope), core));
			
			sharedScope = (ScriptableObject)cx.newObject(coreScope);
			sharedScope.setPrototype(coreScope);
			sharedScope.setParentScope(null);
			
			
			
			cx.evaluateString(sharedScope, "var importPlugin = function(file){$core.pluginManager.importPlugin(file);};" + FetchFile.readFile(thisContextPath+ "/shared.js", ""), thisContextPath+ "/shared.js" , 1, null);
			
			loadModules(modulesPath, cx, true);
			
			try{
				startModuleDirWatcher(modulesPath);
				startUrlsWatcher();
			} catch(Exception e){
				throw new ServletException(e);
			} 
			
			
				
			((Function)core.get("init", core)).call(cx, core, core, null);	
						
		} finally {
			Context.exit();
		}
	}
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException	{
	
		final long start = System.currentTimeMillis();
						
		String uri = request.getRequestURI();
		
		if(urlsArray == null)
		{
			try{
				setURLs();
			} catch (JSONException e) {
				throw new ServletException(e);
			}
		}
		RoutingResult routingResult = routing.route(uri, urlsArray, getServletContext().getContextPath());
		
		if(routingResult.getView() != null)
		{
			Context cx = new CustomContextFactory().enterContext();
			
			try
			{
				Scriptable threadScope = cx.newObject(sharedScope);
				threadScope.setPrototype(sharedScope);
				threadScope.setParentScope(null);
				
				envJs.exec(cx, threadScope);
				ScriptableObject core = (ScriptableObject)coreScope.get("$core", threadScope);
							
				Object getOutputArgs[] = {routingResult.getView(), request, response, uri, routingResult.getUriTokensJsonString()};
				Object result = ((Function)core.get("getOutput", threadScope)).call(cx, threadScope, threadScope, getOutputArgs);
				
				response.setCharacterEncoding("UTF-8");
				
				if(!response.isCommitted())
					response.getWriter().println(Context.toString(result));
				
				final long end = System.currentTimeMillis();
				logger.debug(uri + " : Rendered in " + (end - start) + "ms", null);
							
			} catch (Exception e) {
				throw new ServletException(e);
			} finally {
				Context.exit();
			}
		
		} else {
			response.sendError(404);
		}	
		
	}
	
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);
	}
}
